#include "util.h"
#include "log.h"

#ifdef _WIN32
void socket_init()
{
	WORD version;
	WSAData data;

	version = MAKEWORD(2, 0);
	int err = WSAStartup(version, &data);
	if (err)
	{
		log_error("WSAStartup(): %s", evutil_socket_error_to_string(err));
		assert(err == 0);
	}
}

void socket_fini()
{
	WSACleanup();
}
#endif

void log_connection_event(struct bufferevent* bev, short what)
{
	char local_addr[64], peer_addr[64];
	get_local_name(bev, local_addr, sizeof(local_addr));
	get_peer_name(bev, peer_addr, sizeof(peer_addr));

	if (what & BEV_EVENT_CONNECTED)
		log_info("connect successfully: %s<=>%s", local_addr, peer_addr);

	if (what & BEV_EVENT_READING)
		log_error("read error: %s<=>%s", local_addr, peer_addr);

	if (what & BEV_EVENT_WRITING)
		log_error("write error: %s<=>%s", local_addr, peer_addr);

	if (what & BEV_EVENT_EOF)
		log_info("connection close: %s<=>%s", local_addr, peer_addr);

	if (what & BEV_EVENT_TIMEOUT)
		log_error("socket timeout: %s<=>%s", local_addr, peer_addr);

	if (what & BEV_EVENT_ERROR)
		log_error("socket error: %s, %s<=>%s", get_socket_error(bev), local_addr, peer_addr);
}

struct bufferevent* get_peer_bufferevent(struct bufferevent* bev, struct tcp_connection* conn)
{
	assert(bev);
	assert(conn);
	if (bev == conn->local_bev)
		return conn->remote_bev;
	if (bev == conn->remote_bev)
		return conn->local_bev;

	assert(false);
	return NULL;
}

size_t relay_data(struct bufferevent* from, struct bufferevent* to)
{
	char errstr[256];
	char buf[32 * 1024];

	size_t bytes = bufferevent_read(from, buf, sizeof(buf));
	assert(bytes > 0);

	int err = bufferevent_write(to, buf, bytes);
	if (err)
	{
		log_error("write %d bytes to %s error", bytes, get_peer_name(to, errstr, sizeof(errstr)));
		return -1;
	}

	return bytes;
}

void enable_relay(struct tcp_connection* conn)
{
	assert(conn);
	assert(conn->local_bev);
	assert(conn->remote_bev);
	bufferevent_enable(conn->local_bev, EV_READ | EV_WRITE);
	bufferevent_enable(conn->remote_bev, EV_READ | EV_WRITE);
}

struct evconnlistener* tcp_listen(const char* ipaddr, int port, struct event_base* evbase,
	evconnlistener_cb accept_cb, void* accept_cb_args, evconnlistener_errorcb error_cb)
{
	assert(ipaddr);
	assert(port > 0);
	assert(accept_cb);

	struct evconnlistener* listener = NULL;
	evutil_socket_t fd = -1;
	int err = 0;

	do
	{
		struct sockaddr_in addr = { 0 };

		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr(ipaddr);
		addr.sin_port = htons((unsigned short)port);

		fd = socket(AF_INET, SOCK_STREAM, 0);
		if (fd == (evutil_socket_t)-1)
			break;

		err = evutil_make_listen_socket_reuseable(fd);
		if (err)
			break;

		err = evutil_make_socket_nonblocking(fd);
		if (err)
			break;

		err = bind(fd, (sockaddr*)&addr, sizeof(addr));
		if (err)
			break;

		listener = evconnlistener_new(evbase, accept_cb, accept_cb_args, LEV_OPT_CLOSE_ON_FREE, 1024, fd);
		if (!listener)
			break;

		if (error_cb)
			evconnlistener_set_error_cb(listener, error_cb);
		
		evconnlistener_enable(listener);
		log_info("listening at %s:%d", ipaddr, port);
		return listener;
	} while (0);

	log_socket_error(socket_last_error(), "listening failed at %s:%d", ipaddr, port);

	if (fd != -1)
		evutil_closesocket(fd);

	if (listener)
		evconnlistener_free(listener);

	return NULL;
}

struct bufferevent* tcp_connect(struct sockaddr_in* addr, struct event_base* evbase,
	bufferevent_data_cb read_cb, bufferevent_data_cb write_cb,
	bufferevent_event_cb event_cb, void* cb_args)
{
	assert(addr);
	assert(evbase);

	struct bufferevent* bev = NULL;
	int err = 0;

	char buf[64];
	evutil_inet_ntop(addr->sin_family, &addr->sin_addr.s_addr, buf, sizeof(buf));

	do
	{
		bev = bufferevent_socket_new(evbase, -1, BEV_OPT_CLOSE_ON_FREE);
		if (!bev)
			break;

		bufferevent_setcb(bev, read_cb, write_cb, event_cb, cb_args);
		err = bufferevent_socket_connect(bev, (struct sockaddr*)addr, sizeof(*addr));
		if (err)
			break;

		log_info("launch connect request to %s:%d successfully.", buf, ntohs(addr->sin_port));
		return bev;
	} while (0);

	log_socket_error(socket_last_error(), "launch connect request to %s:%d failed.", buf, ntohs(addr->sin_port));

	if (bev)
		bufferevent_free(bev);

	return NULL;
}

struct bufferevent* tcp_accept(struct event_base* evbase, evutil_socket_t fd, 
	bufferevent_data_cb read_cb, bufferevent_data_cb write_cb,
	bufferevent_event_cb event_cb, void* cb_args)
{
	assert(evbase);
	assert(fd > 0);

	struct bufferevent* bev = NULL;

	do
	{
		bev = bufferevent_socket_new(evbase, fd, BEV_OPT_CLOSE_ON_FREE);
		if (!bev)
			break;
		
		bufferevent_setcb(bev, read_cb, write_cb, event_cb, cb_args);

		char buf[64];
		get_local_name(bev, buf, sizeof(buf));

		log_info("accept new connection from %s successfully.", buf);
		return bev;
	} while (0);

	log_socket_error(socket_last_error(), "accept new connection failed.");	
	if (bev)
		bufferevent_free(bev);

	return NULL;
}

static char* get_local_name(evutil_socket_t fd, char* buf, size_t len)
{
	assert(buf);
	assert(len > 0);

	char ip[64] = { 0 };

	struct sockaddr_in addr = { 0 };
	socklen_t socklen = sizeof(addr);
	int err = getsockname(fd, (struct sockaddr*)&addr, &socklen);
	if (err)
		strncpy(ip, "[not bound]", sizeof(ip));
	else
		evutil_inet_ntop(addr.sin_family, &addr.sin_addr.s_addr, ip, sizeof(ip));

	snprintf(buf, len, "%s:%d", ip, ntohs(addr.sin_port));
	buf[len - 1] = 0;
	return buf;
}

static char* get_peer_name(evutil_socket_t fd, char* buf, size_t len)
{
	assert(buf);
	assert(len > 0);
	
	char ip[64] = { 0 };

	struct sockaddr_in addr = { 0 };
	socklen_t socklen = sizeof(addr);

	int err = getpeername(fd, (struct sockaddr*)&addr, &socklen);
	if (err)
		strncpy(buf, "[not connected]", len);
	else
	{
		evutil_inet_ntop(addr.sin_family, &addr.sin_addr.s_addr, ip, sizeof(ip));
		snprintf(buf, len, "%s:%d", ip, ntohs(addr.sin_port));
	}
	
	buf[len - 1] = 0;
	return buf;
}

char* get_local_name(bufferevent* bev, char* buf, size_t len)
{
	assert(bev);
	assert(buf);
	assert(len > 0);

	evutil_socket_t fd = bufferevent_getfd(bev);
	assert(fd > 0);

	return get_local_name(fd, buf, len);
}

char* get_peer_name(bufferevent* bev, char* buf, size_t len)
{
	assert(bev);
	assert(buf);
	assert(len > 0);

	evutil_socket_t fd = bufferevent_getfd(bev);
	assert(fd > 0);

	return get_peer_name(fd, buf, len);
}

const char* get_socket_error(struct bufferevent* bev)
{
	assert(bev);

	evutil_socket_t fd = bufferevent_getfd(bev);
	assert(fd > 0);

	int err = 0;
	socklen_t len = sizeof(err);

	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (char*)&err, &len))
	{
		log_socket_error(socket_last_error(), "getsockopt");
		return "";
	}

	return evutil_socket_error_to_string(err);
}

int get_dest_addr(struct bufferevent* bev, struct sockaddr_in* addr)
{
#if defined(__linux)
	evutil_socket_t fd = bufferevent_getfd(bev);
	socklen_t len = sizeof(*addr);
	int err = getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, addr, &len);
	if (err)
	{
		log_socket_error(socket_last_error(), "getsockopt()");
		return -1;
	}
	return 0;
#elif defined(_WIN32)
	//test only
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = inet_addr("10.82.49.59");
	addr->sin_port = htons(80);
	return 0;
#else
#error "not supported"
#endif
}

tcp_connection* create_connection(size_t private_size)
{
	tcp_connection* conn = (tcp_connection*)calloc(sizeof(tcp_connection) + private_size, 1);
	assert(conn);
	return conn;
}

void destroy_connection(tcp_connection* conn)
{
	assert(conn);
	char local[128] = { 0 };
	char remote[128] = { 0 };

	if (conn->local_bev)
	{
		get_peer_name(conn->local_bev, local, sizeof(local));
		bufferevent_free(conn->local_bev);
	}
	if (conn->remote_bev)
	{
		get_peer_name(conn->remote_bev, remote, sizeof(remote));
		bufferevent_free(conn->remote_bev);
	}

	log_info("close connection: %s <-> %s", local, remote);
	free(conn);
}

